#include "shmipc/com/uds_client.h"

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
#include <errno.h>
#include <arpa/inet.h>
#include <future>

#include "shmipc/core/xtime.h"
#include "shmipc/com/internal/uds_com.h"
#include "shmipc/com/internal/xsocket.h"

#define GET_SEND_MSG_TIMEOUT_MS (10)
#define SOCK_RECV_TIMEOUT_MS (10)

namespace shmipc::com {
using namespace shmipc::core;



UdsClient::UdsClient() {
    //todo something 
    on_event_handler_ = std::bind(&UdsClient::onEvent, this, std::placeholders::_1);
}

UdsClient::~UdsClient() {
    //todo something 
}


XResultCode UdsClient::connect(const std::string& url, int32_t port /*= 0*/, OnConnectStatusHandler on_handler /*= nullptr*/) {
    if (uds_session_) { return XResultCode::FromError(XErrC::InvalidStatus); }

    auto res = internal::XSocketClientOpen(internal::XSocketType::UDS, url, port);

    if (!res) { return XResultCode::FromError(res.error()); }

    uds_session_ = std::make_shared<internal::UdsSession>(res.value(), url, [](internal::UdsSessionSp ssp) {
        internal::XSocketClose(ssp->session_id());
    });

    uds_session_->setOnRecvHandler([this](XMessageSp msg) {
        this->onMessage(msg);
    });

    uds_session_->setOnErrorHandler([this](XErrC errc){
        if (errc == XErrC::Disconnected) {
            if (this->on_connect_status_) { this->on_connect_status_(false); }
        }
    });

    auto ret = uds_session_->start();
    if (!ret) { 
        uds_session_ = nullptr;
        return ret; 
    }

    is_connected_ = true;
    if (on_handler) {
        on_connect_status_ = on_handler;
        on_handler(true);
    }
    return { };

}

XResultCode UdsClient::disconnect() {
    if (!uds_session_) { return XResultCode::FromError(XErrC::InvalidStatus); }
    uds_session_->stop();
    is_connected_ = false;
    if (on_connect_status_) { on_connect_status_(false); }
    uds_session_ = nullptr;
    return { };
}

bool UdsClient::is_connected() {
    return is_connected_;
}

XResultCode UdsClient::setOnEventHandler(OnMessageHandler handler) {
    on_event_handler_ = handler ? handler : std::bind(&UdsClient::onEvent, this, std::placeholders::_1);
    return { };
}

XResult<XMessageSp> UdsClient::request(XMessageSp param, int32_t timeout_ms /*= core::WaitType::WaitForever*/) {
    std::shared_ptr<std::promise<XMessageSp>> prom = std::make_shared<std::promise<XMessageSp>>();
    std::future<XMessageSp> res = prom->get_future();
    auto ret = requestAsync(param, [prom](XMessageSp msg){
        prom->set_value(msg);
    });

    if (ret) {
        if (timeout_ms == WaitType::WaitForever) {
            return {res.get()};
        } else {
            std::future_status status = res.wait_for(std::chrono::milliseconds(timeout_ms));
            if (status == std::future_status::ready) {
                return {res.get()};
            } else {
                return XResult<XMessageSp>::FromError(XErrC::TimeOut);
            }
        }
    } else {
        prom->set_value(nullptr);
    }

    return XResult<XMessageSp>::FromError(ret.error());
}

XResultCode UdsClient::requestAsync(XMessageSp param, OnMessageHandler on_response_handler /*= nullptr*/) {
    if (!uds_session_) { return XResultCode::FromError(XErrC::InvalidStatus); }
    uint16_t seq_num = generate_seq_num();
    param->set_seq_num(seq_num);
    if (on_response_handler) {
        on_response_map_[param->type()][seq_num] = on_response_handler; //todo refine me??
    }
    
    return uds_session_->send(param);
}

void UdsClient::onEvent(XMessageSp msg){ //Messages outside of request/requestAsync 
    static_cast<void>(msg);
    //todo something
}

void UdsClient::onMessage(XMessageSp msg) {
    auto on_msg_handler = popMsgHandler(msg->type(), msg->seq_num());
    if (on_msg_handler) {
        on_msg_handler(msg);
    } else {
        on_event_handler_(msg);
    }
}


UdsClient::OnMessageHandler UdsClient::popMsgHandler(uint8_t type, SequenceNumber seq_num) {
    auto it = on_response_map_.find(type);
    if (it == on_response_map_.end()) { return nullptr; }

    auto& handler_map = it->second;
    auto handler = handler_map.find(seq_num);
    if (handler == handler_map.end()) { return nullptr; }

    auto on_msg = handler->second;
    handler_map.erase(handler);
    if (handler_map.empty()) { on_response_map_.erase(it); }

    return on_msg;
}

uint16_t UdsClient::generate_seq_num() {
    return next_seq_num_++;
}

} //namespace shmipc::com